package com.senseuni.da.iot

import android.os.Environment
import android.util.Base64
import com.elvishew.xlog.XLog
import com.google.gson.Gson
import com.jeremyliao.liveeventbus.LiveEventBus
import com.senseuni.da.MyApplication
import com.senseuni.da.db.AppDatabase
import com.senseuni.da.db.dao.MediaDao
import com.senseuni.da.db.entity.MediaEntity
import com.senseuni.da.events.MessageEvent
import com.senseuni.da.events.MyEvent
import com.senseuni.da.model.InvokeType
import com.senseuni.da.utils.*
import info.mqtt.android.service.MqttAndroidClient
import kotlinx.coroutines.*
import org.eclipse.paho.client.mqttv3.*
import org.greenrobot.eventbus.EventBus
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.util.*


/**
 * ================================================
 * Copyright (c) 2020 All rights reserved
 * 描述：
 * Author: Yuri
 * Date： Date: 2024/12/7
 * ================================================
 */
class MQTTManager private constructor() {
    private lateinit var mqttAndroidClient: MqttAndroidClient

    private lateinit var mMqttInfo: MqttInfo

    private lateinit var mMediaDao: MediaDao

    private val coroutineScope = CoroutineScope(Job() + Dispatchers.IO)

    companion object {
        val instance: MQTTManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            MQTTManager()
        }
    }

    fun init(mqttInfo: MqttInfo) {
        this.mMqttInfo = mqttInfo
        XLog.d("MQTT.Server:%s,clientId:%s", mqttInfo.host, mqttInfo.clientId)
        mqttAndroidClient = MqttAndroidClient(
            MyApplication.app.applicationContext,
            mqttInfo.host,
            mqttInfo.clientId
        )
        mqttAndroidClient.setCallback(object : MqttCallback {
            override fun connectionLost(cause: Throwable) {
                cause.printStackTrace()
                XLog.d("connectionLost: ", cause)
            }

            override fun messageArrived(topic: String, message: MqttMessage) {
                XLog.d("messageArrived.topic:$topic,messageId:${message.id}")
                val msg = String(message.payload)
                if (topic.contains("invoke")) {
                    try {
                        val msgBean = Gson().fromJson(msg, InvokeMessageBean::class.java)
                        if ((msgBean.functionId ?: "") == InvokeType.PUT_PICTURE.typeId) {
                            XLog.d("messageArrived: topic:$topic,message:${msgBean.toString()}")
                        } else {
                            XLog.d("messageArrived: topic:$topic,message:$msg")
                        }
                        handleInvoke(topic, msgBean)
                    } catch (e: Exception) {
                        XLog.e("invoke error:${e.message}")
                    }
                    return
                }
                XLog.d("messageArrived: topic:$topic,message:$msg")
                if (topic.contains("properties/write")) {
                    try {
                        val msgBean = Gson().fromJson(msg, WriteMessageBean::class.java)
                        handleWrite(topic, msgBean)
                    } catch (e: Exception) {
                        XLog.e("properties/write error:${e.message}")
                    }
                    return
                }
                if (topic.contains("properties/read")) {
                    try {
                        if (msg.contains("location_info")) return
                        val msgBean = Gson().fromJson(msg, ReadMessageBean::class.java)
                        handleRead(topic, msgBean)
                    } catch (e: Exception) {
                        XLog.e("properties/read error:${e.message}")
                    }
                    return
                }

            }

            override fun deliveryComplete(token: IMqttDeliveryToken) {
                XLog.d("deliveryComplete")
            }
        })
        mqttAndroidClient.apply {
            val notificationHelper = NotificationHelper()
            val notification = notificationHelper.createNotification()
            setForegroundService(notification, 1)
        }

        connect(mqttInfo.username, mqttInfo.password)

        mMediaDao = AppDatabase.getInstance().mediaDao()
    }

    private fun connect(username: String, password: String) {
        val connectOptions = MqttConnectOptions()
        connectOptions.userName = username
        connectOptions.password = password.toCharArray()
        connectOptions.isAutomaticReconnect = true
        XLog.d("username:%s,password:%s", username, password)
        try {
            mqttAndroidClient.connect(connectOptions, null, object : IMqttActionListener {
                override fun onSuccess(asyncActionToken: IMqttToken) {
                    XLog.d("MQTT Connect Success")
                    EventBus.getDefault().post(MessageEvent("mqtt connect Success"))
                    EventBus.getDefault().post(MessageEvent.initOver())

                    reportBasic()
                }

                override fun onFailure(asyncActionToken: IMqttToken, exception: Throwable) {
                    exception.printStackTrace()
                    XLog.e("onFailure: MQTT Connect Fail：%s", exception.message)
                }
            })
        } catch (e: MqttException) {
            e.printStackTrace()
            XLog.e("initMQTTClient: Error", e)
        }
    }

    private fun handleRead(topic: String, msg: ReadMessageBean) {
        val replyTopic = "${topic}/reply"

        val timestamp = System.currentTimeMillis()
        val map = HashMap<String, Any>()
        map["sn"] = "11111"
        map["model"] = "222222222"
        map["version"] = "1.0.0"
        map["version_code"] = 1000
        map["cpuid"] = "11111"
        val replyMsg =
            ReplyMessageBean(timestamp, msg.messageId, "REPORT_PROPERTY", msg.deviceId, true, map)

        publishMessage(replyTopic, Gson().toJson(replyMsg))

    }

    private fun handleWrite(topic: String, msg: WriteMessageBean) {
        val appStartTime = AppUtils.getAppStartTime()
        if (appStartTime > msg.timestamp) {
            XLog.d("outtime msg,ignore...")
            return
        }

        if (msg.properties == null) {
            XLog.e("properties is null")
            return
        }

        val map = msg.properties!!
        for (entry in map.entries) {
            if ("volume" == entry.key) {
                val value = entry.value?.toString()?.toDoubleOrNull() ?: continue
                AppUtils.saveVolume(value.toInt())
                AppUtils.setVolume(value.toInt())
                //设置完音量后，上报一次音量数据
                reportProperty("volume", value)
            } else if ("language" == entry.key) {
                val value = entry.value?.toString() ?: continue
//                EventUtil.sendLanguageEvent(value)
                LanguageHelper.switchLanguage(value)
            }
        }
        val replyTopic = "${topic}/reply"
        val replyMessageBean = ReplyMessageBean()
        replyMessageBean.success = true
        replyMessageBean.timestamp = System.currentTimeMillis()
        replyMessageBean.messageId = msg.messageId
        publishMessage(replyTopic, replyMessageBean.toJson())
    }

    private fun handleInvoke(topic: String, msg: InvokeMessageBean) {
        val appStartTime = AppUtils.getAppStartTime()
        if (appStartTime > msg.timestamp) {
            XLog.d("outtime msg,ignore...")
            return
        }
        val replyTopic = "${topic}/reply"
        val replyMessageBean = InvokeReplyMessageBean()
        replyMessageBean.messageId = msg.messageId
        replyMessageBean.timestamp = System.currentTimeMillis()
        if (msg.inputs == null || msg.inputs?.isEmpty() == true) {
            XLog.e("No inputs data")
            replyMessageBean.success = false
            replyMessageBean.code = "-1"
            replyMessageBean.message = "No inputs data"
            publishMessage(replyTopic, replyMessageBean.toJson())
            return
        }

        val async = msg.headers?.async ?: false

        when (msg.functionId) {
            InvokeType.REBOOT.typeId -> {
                //sync
                XLog.d("INVOKE REBOOT")
                val rebootType = msg.inputs?.find { it["name"] == "reboot_type" }
                    ?.get("value")
                    ?.let { (it as? Double)?.toInt() ?: -1 } ?: -1
                XLog.d("INVOKE REBOOT.rebootType=%d", rebootType)
                if (rebootType == -1) {
                    XLog.e("Reboot type not found or invalid")
                    replyMessageBean.success = false
                    replyMessageBean.code = "-1"
                    replyMessageBean.message = "Reboot type not found or invalid"
                    publishMessage(replyTopic, replyMessageBean.toJson())
                    return
                }

                if (rebootType != 1 && rebootType != 2) {
                    XLog.e("Reboot type not supported")
                    replyMessageBean.success = false
                    replyMessageBean.code = "-1"
                    replyMessageBean.message = "Reboot type not supported"
                    publishMessage(replyTopic, replyMessageBean.toJson())
                    return
                }

                replyMessageBean.output = "success"
                replyMessageBean.success = true

                publishMessage(replyTopic, replyMessageBean.toJson())

                if (rebootType == 1) {
                    AppUtils.reboot()
                } else {
                    AppUtils.rebootApp()
                }
            }

            InvokeType.SYSTEM.typeId -> {
                //async
                XLog.d("INVOKE SYSTEM")
                //0重置配置 1格式化存储空间 2恢复出厂 3清空缓存
                val operate = msg.inputs?.find { it["name"] == "operate" }
                    ?.get("value")
                    ?.let { (it as? Double)?.toInt() ?: -1 } ?: -1
                XLog.d("INVOKE SYSTEM.operate=%d", operate)

                if (operate == -1) {
                    XLog.e("operate not found or invalid")
                    replyMessageBean.success = false
                    replyMessageBean.code = "-1"
                    replyMessageBean.message = "operate not found or invalid"
                    publishMessage(replyTopic, replyMessageBean.toJson())
                    return
                }

                replyMessageBean.output = "success"
                replyMessageBean.success = true
                publishMessage(replyTopic, replyMessageBean.toJson())

                //0重置配置 1格式化存储空间 2恢复出厂 3清空缓存 4 关机 (息屏关声音) 5 开机 (亮屏开声音)
                when (operate) {
                    0 -> {
//                        AppUtils.resetConfig()
                    }
                    1 -> {
//                        AppUtils.formatStorage()
                    }
                    2 -> {
//                        AppUtils.restoreFactory()
                    }
                    3 -> {
//                        AppUtils.clearCache()
                    }
                    4 -> AppUtils.screenOff()
                    5 -> AppUtils.screenOn()
                    else -> {
                        XLog.e("wrong operate:${operate}")
                    }
                }
            }

            InvokeType.UPGRADE.typeId -> {
                //async
                XLog.d("INVOKE UPGRADE")
                val upgradeType = msg.inputs?.find { it["name"] == "upgrade_type" }
                    ?.get("value")
                    ?.let { (it as? Double)?.toInt() ?: -1 } ?: -1
                XLog.d("INVOKE UPGRADE.upgradeType=%d", upgradeType)
                //1:操作系统升级，2:APP升级
                if (upgradeType == -1) {
                    XLog.e("upgrade type not found or invalid")
                    replyMessageBean.success = false
                    replyMessageBean.code = "-1"
                    replyMessageBean.message = "upgrade type not found or invalid"
                    publishMessage(replyTopic, replyMessageBean.toJson())
                    return
                }

                if (upgradeType != 2) {
                    //system upgrade not supported
                    replyMessageBean.success = false
                    replyMessageBean.code = "-1"
                    replyMessageBean.message = "upgrade type not supported"
                    publishMessage(replyTopic, replyMessageBean.toJson())
                    return
                }

                replyMessageBean.output = "success"
                replyMessageBean.success = true
                publishMessage(replyTopic, replyMessageBean.toJson())

                EventUtil.sendUpgradeEvent()
            }

            InvokeType.PUT_PICTURE.typeId -> {
                //async
                XLog.d("INVOKE PUT_PICTURE")
                coroutineScope.launch {
                    try {
                        withContext(Dispatchers.IO) {
                            handlePutPicture(topic, msg)
                        }
                    } catch (e: CancellationException) {
                        XLog.e("Fetch operation was cancelled.")
                        sendReplyFail(msg.messageId ?: "", topic, "Fetch operation was cancelled.")
                    } catch (e: Exception) {
                        XLog.e("Error fetching data: ${e.message}")
                        sendReplyFail(msg.messageId ?: "", topic, e.message ?: "未知异常")
                    }
                }
            }

            InvokeType.GET_PICTURE.typeId -> {
                coroutineScope.launch {
                    try {
                        withContext(Dispatchers.IO) {
                            handleGetPicture(topic, msg)
                        }
                    } catch (e: CancellationException) {
                        XLog.e("Fetch operation was cancelled.")
                        sendReplyFail(msg.messageId ?: "", topic, "Fetch operation was cancelled.")
                    } catch (e: Exception) {
                        XLog.e("Error fetching data: ${e.message}")
                        sendReplyFail(msg.messageId ?: "", topic, e.message ?: "未知异常")
                    }
                }
            }

            InvokeType.REMOVED_PICTURE.typeId -> {
                val uuid = msg.inputs?.find { it["name"] == "uuid" }
                    ?.get("value") as? String ?: ""
                XLog.d("INVOKE REMOVED_PICTURE:$uuid")
                val mediaEntity = mMediaDao.findByUUID(uuid)
                if (mediaEntity == null) {
                    XLog.e("mediaEntity not found")
                    replyMessageBean.success = false
                    replyMessageBean.code = "-1"
                    replyMessageBean.message = "image resource not found"
                    publishMessage(replyTopic, replyMessageBean.toJson())
                    return
                }

                try {
                    val file = File(mediaEntity.path)
                    if (file.exists()) {
                        val ret = file.delete()
                        if (!ret) {
                            XLog.e("delete local file failed")
                            replyMessageBean.success = false
                            replyMessageBean.code = "-1"
                            replyMessageBean.message = "delete local file failed"
                            publishMessage(replyTopic, replyMessageBean.toJson())
                            return
                        }
                        val ret1 = mMediaDao.deleteByUUID(uuid)
                        if (ret1 == 0) {
                            XLog.e("delete db failed")
                            replyMessageBean.success = false
                            replyMessageBean.code = "-1"
                            replyMessageBean.message = "delete db failed"
                            publishMessage(replyTopic, replyMessageBean.toJson())
                            return
                        }
                        LiveEventBus.get<Long>(MyEvent.KEY_IMAGE_DELETE).post(mediaEntity.id)
                    }
                } catch (e: Exception) {
                    XLog.e("delete media fail:" + e.message)
                    replyMessageBean.success = false
                    replyMessageBean.code = "-1"
                    replyMessageBean.message = "delete image fail:" + e.message
                    publishMessage(replyTopic, replyMessageBean.toJson())
                    return
                }

                replyMessageBean.output = "success"
                replyMessageBean.success = true
                publishMessage(replyTopic, replyMessageBean.toJson())
            }
        }
    }

    private fun handlePutPicture(topic: String, msg: InvokeMessageBean) {
        var base64 = msg.inputs?.find { it["name"] == "base64" }
            ?.get("value") as? String ?: ""
        val key = msg.inputs?.find { it["name"] == "key" }
            ?.get("value") as? String ?: ""
        val username = msg.inputs?.find { it["name"] == "user_name" }
            ?.get("value") as? String ?: ""
        val timestamp = msg.inputs?.find { it["name"] == "timestamp" }
            ?.get("value")
            ?.let { (it as? Double)?.toLong() ?: 0 } ?: 0
        val remark = msg.inputs?.find { it["name"] == "remark" }
            ?.get("value") as? String ?: ""
        val uuid = msg.inputs?.find { it["name"] == "uuid" }
            ?.get("value") as? String ?: ""
        //文件类型（1：图片，2：视频）
        val fileType = msg.inputs?.find { it["name"] == "file_type" }
            ?.get("value")
            ?.let { (it as? Double)?.toInt() ?: 0 } ?: 0
        val playTime = msg.inputs?.find { it["name"] == "play_time" }
            ?.get("value")
            ?.let { (it as? Double)?.toLong() ?: 0 } ?: 0
        XLog.d("INVOKE PUT_PICTURE.uuid:${uuid},username:${username},timestamp:${timestamp},remark:${remark},fileType:${fileType},playTime:${playTime}")
        //解密source
        val messageId = msg.messageId ?: ""
        try {
            base64 = MediaUtil.decrypt(base64, key)
        } catch (e: Exception) {
            XLog.e("base64 decrypt error:" + e.message)
            sendReplyFail(messageId, topic, "base64 decrypt error:" + e.message)
            return
        }

        base64 = AppUtils.removeBase64Header(base64)
        if (fileType != 1 && fileType != 2) {
            XLog.e("fileType not supported")
            sendReplyFail(messageId, topic, "fileType:${fileType} not supported")
            return
        }

        val cacheDir = Environment.getExternalStorageDirectory()
        val mediaEntity = MediaEntity().apply {
            this.uuid = uuid
            this.username = username
            this.timestamp = timestamp
            this.remark = remark
            this.duration = playTime
            this.source = "iot"
        }

        val file: File
        if (fileType == 2) {
            //save video
            val videoDir = "${cacheDir.absolutePath}/lumx/videos";
            val videoDirFile = File(videoDir)
            if (!videoDirFile.exists()) {
                videoDirFile.mkdirs()
            }
            file = File(videoDir, "${System.currentTimeMillis()}.mp4")
            try {
                val fos = FileOutputStream(file)
                fos.write(Base64.decode(base64, Base64.DEFAULT))
                fos.flush()
                fos.close()
            } catch (e: FileNotFoundException) {
                XLog.e("video base64 save fail")
                sendReplyFail(messageId, topic, "video base64 save fail")
                return
            }
            mediaEntity.mimeType = "video"
            mediaEntity.fileName = file.name
            mediaEntity.fileSize = file.length()
            mediaEntity.path = file.absolutePath
        } else {
            //save image
            val imageDir = "${cacheDir.absolutePath}/lumx/images";
            val imageDirFile = File(imageDir)
            if (!imageDirFile.exists()) {
                imageDirFile.mkdirs()
            }

            val result = MediaUtil.convertBase64ToImage(base64, imageDir, mediaEntity)
            if (result.isNotEmpty()) {
                sendReplyFail(messageId, topic, result)
                return
            }
        }

        mediaEntity.createDate = System.currentTimeMillis()
        mMediaDao.insert(mediaEntity)

        XLog.d("put_picture handle success")
//                MediaScannerConnection.scanFile(MyApplication.app.applicationContext, arrayOf(file.absolutePath), arrayOf("image/jpeg")) { path, uri ->
//                    XLog.d("scan file success:$path")
////                    EventUtil.sendDownloadEvent(mediaEntity.id, mediaEntity.path)
//                }

        LiveEventBus.get<MediaEntity>(MyEvent.KEY_IMAGE_ADD).post(mediaEntity)

        sendReplySuccess(messageId, topic)
    }

    private fun handleGetPicture(topic: String, msg: InvokeMessageBean) {
        val uuid = msg.inputs?.find { it["name"] == "uuid" }
            ?.get("value") as? String ?: ""
        XLog.d("INVOKE GET_PICTURE:$uuid")
        val messageId = msg.messageId ?: ""
        val mediaEntity = mMediaDao.findByUUID(uuid)
        if (mediaEntity == null) {
            XLog.e("mediaEntity not found")
            sendReplyFail(messageId, topic, "image resource not found")
            return
        }

        if (mediaEntity.mimeType == "video") {
            XLog.e("video not supported")
            sendReplyFail(messageId, topic, "video resource not supported")
            return
        }

        var base64 = MediaUtil.convertImageToBase64(mediaEntity.path)
        if (base64 == null) {
            XLog.e("convert image to base64 fail")
            sendReplyFail(messageId, topic, "convert image to base64 fail")
            return
        }
        val encrypt = MediaUtil.encrypt(base64)

        base64 = encrypt[0]
        val key = encrypt[1]

        XLog.d("convert image to base64 success,key:$key")
        val pictureBean = GetPictureBean(base64, key)

        sendReplySuccess(messageId, topic, pictureBean.toJson())
    }

    private fun sendReplyFail(messageId: String, topic: String, msg: String) {
        val replyTopic = "${topic}/reply"
        val replyMessageBean = InvokeReplyMessageBean()
        replyMessageBean.messageId = messageId
        replyMessageBean.timestamp = System.currentTimeMillis()
        replyMessageBean.success = false
        replyMessageBean.code = "-1"
        replyMessageBean.message = msg
        publishMessage(replyTopic, replyMessageBean.toJson())
    }

    private fun sendReplySuccess(messageId: String, topic: String, output:String = "success") {
        val replyTopic = "${topic}/reply"
        val replyMessageBean = InvokeReplyMessageBean()
        replyMessageBean.messageId = messageId
        replyMessageBean.timestamp = System.currentTimeMillis()
        replyMessageBean.success = true
        replyMessageBean.output = output
        publishMessage(replyTopic, replyMessageBean.toJson())
    }

    private fun getReportTopic(): String {
        val productId = mMqttInfo.productId
        val deviceId = mMqttInfo.deviceId
        return "/${productId}/${deviceId}/properties/report"
    }

    fun reportBasic() {
        //TODO 这里最好子线程实现
        XLog.d("Report basic")
        val deviceId = mMqttInfo.deviceId
        val topic = getReportTopic()
        val timestamp = System.currentTimeMillis()
        val context = MyApplication.app.applicationContext
        val map = HashMap<String, Any>()
        map["device_basics"] = PropertyUtil.createDeviceBasic()
        map["cm_basics"] = PropertyUtil.createCMBasic(context)
        map["system"] = PropertyUtil.createSystemInfo(context)
        map["volume"] = PropertyUtil.createVolumeProp(context)
        map["display_basics"] = PropertyUtil.createDisplayProp(context)
        map["language"] = LanguageHelper.getLanguage()
        val reportMsg = ReplyMessageBean(timestamp, null, "REPORT_PROPERTY", deviceId, null, map)
        publishMessage(topic, reportMsg.toJson())
    }

    private fun reportProperty(key: String, params: Any) {
        val deviceId = mMqttInfo.deviceId
        val topic = getReportTopic()
        val timestamp = System.currentTimeMillis()
        val map = HashMap<String, Any>()
        map[key] = params
        val reportMsg = ReplyMessageBean(timestamp, null, "REPORT_PROPERTY", deviceId, null, map)
        publishMessage(topic, reportMsg.toJson())
    }

    fun reportAdEvent() {
        val productId = mMqttInfo.productId
        val deviceId = mMqttInfo.deviceId
        val topic = "/${productId}/${deviceId}/event/advertisement_situation";
        val messageId = UUID.randomUUID().toString()
        val adBean = AdvertisementSituationEvent("1111", "2222", "success")
        val eventBean = EventMessageBean(messageId = messageId, data = adBean)
        publishMessage(topic, eventBean.toJson())
    }

    private fun subscribeTopic(topic: String) {
        try {
            mqttAndroidClient.subscribe(topic, 0, null, object : IMqttActionListener {
                override fun onSuccess(asyncActionToken: IMqttToken) {
                    XLog.d("subscribeTopic success")
                }

                override fun onFailure(asyncActionToken: IMqttToken, exception: Throwable) {
                    XLog.e("subscribeTopic failed", exception)
                }
            })
        } catch (e: MqttException) {
            e.printStackTrace()
            XLog.e("subscribeTopic: error", e)
        }
    }

    private fun publishMessage(topic: String, message: String) {
        XLog.d("topic:%s,message:%s", topic, message)
        val mqttMessage = MqttMessage()
        mqttMessage.payload = message.toByteArray()
        mqttMessage.qos = 1
        try {
            mqttAndroidClient.publish(topic, mqttMessage, null, object : IMqttActionListener {
                override fun onSuccess(asyncActionToken: IMqttToken) {
                    XLog.d("publishMessage success:$message")
                }

                override fun onFailure(asyncActionToken: IMqttToken, exception: Throwable) {
                    XLog.e("publishMessage failed:%s", exception.toString())
                }
            })
        } catch (e: MqttException) {
            e.printStackTrace()
            XLog.e("publishMessage: error:%s", e.message)
        }
    }

}